同像性的本质
同像性语言:一种其内部表示形式直接以该语言自身表达的语言。在 Elixir 中,源代码不仅仅是文本;它是一个嵌套的数据结构,称为 抽象语法树(AST)。
1. 表示的逻辑规则
Elixir 通过将基本字面量直接表示为自身来简化抽象语法树。原子、数字、列表(包括关键字列表)、二进制数据以及两个元素的元组,在内部都无需复杂包装即可表示。
iex> quote do: [do: 1]
[do: 1]
iex> quote do: "binaries"
"binaries"
[do: 1]
iex> quote do: "binaries"
"binaries"
2. 抽象语法树的元组结构
大多数代码转换都会生成一个三元素元组: {函数名, 元数据, 参数}。例如,一个函数调用如 1 + 2 会变为 {:+, [line: 1], [1, 2]}。这使得语言能够将 代码视为数据。
3. 宏:延迟执行
宏是“通过将代码注入程序内部表示中,从而推迟语句执行的一种方式。”与标准函数在调用前评估参数不同,宏接收的是 之前 调用时,宏接收的是 原始抽象语法树,从而可在编译前进行逻辑注入。
$$\text{源代码} \xrightarrow{\text{quote}} \text{AST 元组} \xrightarrow{\text{宏}} \text{转换后的 AST}$$
main.py
TERMINALbash — 80x24
> Ready. Click "Run" to execute.
>
QUESTION 1
Which of the following is represented internally as itself in Elixir's AST?
A function definition
A list of integers
An if/else expression
A module alias
✅ Correct!
Correct! Atoms, numbers, lists, binaries, and two-element tuples are represented as themselves.❌ Incorrect
Complex expressions like 'if' are represented as three-element tuples. Lists are literal in the AST.QUESTION 2
What does
Code.string_to_quoted("a + b") return?A binary string "a + b"
{:ok, {:+, [...], [{:a, [...], nil}, {:b, [...], nil}]}}
[a: :+, b: :+]
:ok
✅ Correct!
This is the three-element tuple structure representing the function call, metadata, and arguments.❌ Incorrect
It returns a tuple representing the AST structure of the code provided in the string.QUESTION 3
What is 'Success Typing' in the context of Elixir and Dialyzer?
A requirement that all functions have explicit types.
An inference system that assumes code is correct until it finds a contradiction.
A way to force runtime checks on all variables.
The process of compiling Elixir to BEAM bytecode.
✅ Correct!
Success typing assumes code works and only warns when it can prove a type contradiction exists.❌ Incorrect
Dialyzer is non-intrusive; it infers permissive types rather than requiring strict declarations.QUESTION 4
How does an Umbrella project structure applications?
One massive file with multiple module definitions.
A lightweight mix file with an apps directory containing subprojects.
A series of microservices communicating over TCP.
A cloud-based repository with no local files.
✅ Correct!
Umbrella projects are lightweight containers for multiple independent apps.❌ Incorrect
Umbrellas use the 'apps' directory to store independent mix projects.QUESTION 5
Why would you use the
after clause in a try block?To catch only ArithmeticErrors.
To ensure code runs regardless of whether an exception was raised.
To retry the operation three times.
To define a new macro.
✅ Correct!
The after clause is a finalizer that always executes at the end.❌ Incorrect
It is used for guaranteed cleanup, not for matching specific errors.Case Study: The Homoiconic Parser
Manipulating Elixir AST and Protocols
You are tasked with extending Elixir's core capabilities. You must implement a macro and a protocol that demonstrate how Elixir treats code as data. Consider a system where arithmetic must be explained in plain English and custom data structures (like MIDI frames) must be inspectable.
Q
Exercise: MacrosAndCodeEvaluation-1: Write a macro called myunless that implements the standard unless functionality. You’re allowed to use the regular if expression in it.
Solution:
To implement myunless, we use quote to return an AST that uses 'if' with a negated condition: defmacro myunless(condition, clauses) do quote do if !unquote(condition), do: unquote(clauses[:do]) end end
To implement myunless, we use quote to return an AST that uses 'if' with a negated condition: defmacro myunless(condition, clauses) do quote do if !unquote(condition), do: unquote(clauses[:do]) end end
Q
Exercise: Protocols-1. Write a Caesar protocol for rotating letters in both Lists and Binaries. Include encrypt(string, shift) and rot13(string).
Solution:
Defintion of the protocol: defprotocol Caesar do def encrypt(string, shift) def rot13(string) end Implementation for both types: defimpl Caesar, for: [List, BitString] do def encrypt(string, shift) do # implementation logic here end def rot13(string), do: encrypt(string, 13) end
Defintion of the protocol: defprotocol Caesar do def encrypt(string, shift) def rot13(string) end Implementation for both types: defimpl Caesar, for: [List, BitString] do def encrypt(string, shift) do # implementation logic here end def rot13(string), do: encrypt(string, 13) end
Q
Explain how Dialyzer's Success Typing interacts with custom protocols like Enumerable.
Solution:
Dialyzer knows the types of built-in functions like Enumerable.reduce and can infer your function types from them. It assumes the code is correct until it finds a contradiction (e.g., passing a non-enumerable to Enum.map), allowing it to validate protocol compliance without explicit runtime checks.
Dialyzer knows the types of built-in functions like Enumerable.reduce and can infer your function types from them. It assumes the code is correct until it finds a contradiction (e.g., passing a non-enumerable to Enum.map), allowing it to validate protocol compliance without explicit runtime checks.